package com.cloudbroker.bcs.common.util;

import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

import javax.security.auth.x500.X500Principal;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TODO:功能可以更将细化，覆盖多种签名的封装，包括客户端签名的实现
 */
public class CertUtil {

    private static final Logger LOG = LoggerFactory.getLogger(CertUtil.class);
    public static final String DEFAULT_CHARSET = "ISO-8859-1";

    private static Provider bcProvider;

    static {
        init();
    }

    private static void init() {
        bcProvider = new BouncyCastleProvider();
        Security.addProvider(bcProvider);
    }

    public static KeyPair genKeyPair(String keyAlg, int keySize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlg, bcProvider);

        SecureRandom random = new SecureRandom(new byte[32]);
        keyGen.initialize(keySize, random);

        return keyGen.generateKeyPair();
    }

    /**
     * 生产P10，并生产P8
     * @param dn
     * @param keyAlg    生成钥对的算法  exp:sha1
     * @param keySize   密钥对长度
     * @param sigAlg    签名算法        exp:sha1
     * @param p8StoreOS p8存储位置
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws OperatorCreationException
     */
    public static String genP10AndStoreP8(String dn, String keyAlg, int keySize, String sigAlg,
                                          OutputStream p8StoreOS) throws GeneralSecurityException, IOException, OperatorCreationException {
        ObjectOutputStream priKeyOOS = null;
        try {
            Provider provider = bcProvider;

            // create keyGen
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(keyAlg, provider);
            SecureRandom random = new SecureRandom(new byte[32]);
            keyGen.initialize(keySize, random);

            // create raw keyPair
            KeyPair keyPair = keyGen.generateKeyPair();
            PublicKey pubKey = keyPair.getPublic();
            PrivateKey priKey = keyPair.getPrivate();

            // encode pub/pri key
            KeyFactory keyFac = KeyFactory.getInstance(keyAlg, provider);

            byte[] encodedPubKeyBytes = pubKey.getEncoded();
            X509EncodedKeySpec x509PubKeySpec = new X509EncodedKeySpec(encodedPubKeyBytes);
            PublicKey x509PubKey = keyFac.generatePublic(x509PubKeySpec);

            byte[] encodedPriKeyBytes = priKey.getEncoded();
            PKCS8EncodedKeySpec p8PriKeySpec = new PKCS8EncodedKeySpec(encodedPriKeyBytes);
            PrivateKey p8priKey = keyFac.generatePrivate(p8PriKeySpec);

            // store p8
            if (null != p8StoreOS) {
                ObjectOutputStream oos = new ObjectOutputStream(p8StoreOS);
                oos.writeObject(p8priKey);

                byte[] p8priKeyBytes = p8priKey.getEncoded();
                p8StoreOS.write(p8priKeyBytes);
            }

            // create x500Principal
            X500Principal principal = new X500Principal(dn);

            // create p10r
            PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(principal,
                    x509PubKey);
            PKCS10CertificationRequest p10r = requestBuilder.build(new JcaContentSignerBuilder(sigAlg).setProvider(
                    bcProvider).build(p8priKey));

            // encode p10r as base64 string
            byte[] p10rBytes = p10r.getEncoded();
            String p10rBase64String = Base64.encodeBase64String(p10rBytes);

            return p10rBase64String;
        } finally {
            IOUtil.close(priKeyOOS);
        }
    }
    public static PrivateKey loadP8FromBase64(String keyAlg, String p8Base64) throws NoSuchAlgorithmException,
            InvalidKeySpecException {
        PrivateKey privateKey = null;
        byte[] keyBytes = Base64.decodeBase64(p8Base64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(keyAlg);
        privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }
    public static PublicKey loadPublicKeyFromBase64(String keyAlg, String pubKeyBase64)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        PublicKey publicKey = null;
        byte[] keyBytes = Base64.decodeBase64(pubKeyBase64);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(keyAlg);
        publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }
    public static byte[] sign(String srcInfo, PrivateKey priKey) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException {
        Signature signer = Signature.getInstance(priKey.getAlgorithm(), bcProvider);
        signer.initSign(priKey);
        signer.update(srcInfo.getBytes());
        byte[] signedInfo = signer.sign();
        return signedInfo;
    }
    public static String signAsBase64(String srcInfo, String charset, PrivateKey priKey, String sigAlg)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        byte[] signedInfo = sign(srcInfo, charset, priKey, sigAlg);
        return Base64.encodeBase64String(signedInfo);
    }
    public static String signAsBase64(String srcInfo, PrivateKey priKey) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException {
        byte[] signedInfo = sign(srcInfo, priKey);
        return Base64.encodeBase64String(signedInfo);
    }

    public static byte[] sign(String srcInfo, String charset, PrivateKey priKey, String sigAlg)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        if (StringUtils.isBlank(sigAlg)) {
            sigAlg = priKey.getAlgorithm();
        }
        Signature signer = Signature.getInstance(sigAlg, bcProvider);
        signer.initSign(priKey);
        signer.update(getStringInfoBytes(srcInfo, charset));
        byte[] signedInfo = signer.sign();
        return signedInfo;
    }
    public static byte[] sign(String srcInfo, String charset, PrivateKey priKey) throws NoSuchAlgorithmException,
            InvalidKeyException, SignatureException, UnsupportedEncodingException {
        return sign(srcInfo, charset, priKey, null);
    }
    public static String signAsBase64(String srcInfo, String charset, PrivateKey priKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        byte[] signedInfo = sign(srcInfo, charset, priKey);
        return Base64.encodeBase64String(signedInfo);
    }
    public static boolean verify(String plainInfo, String charset, String signedInfoBase64, Certificate cert,
                                 String sigAlg) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
            UnsupportedEncodingException {
        byte[] signedInfoBytes = Base64.decodeBase64(signedInfoBase64);
        return verify(plainInfo, charset, signedInfoBytes, cert, sigAlg);
    }
    public static boolean verify(String plainInfo, String charset, byte[] signedInfo, Certificate cert,
                                 String sigAlg) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException,
            UnsupportedEncodingException {
        if (StringUtils.isBlank(sigAlg)) {
            sigAlg = cert.getPublicKey().getAlgorithm();
        }
        Signature checker = Signature.getInstance(sigAlg);
        checker.initVerify(cert);
        checker.update(getStringInfoBytes(plainInfo, charset));
        return checker.verify(signedInfo);
    }

    private static byte[] getStringInfoBytes(String stringInfo, String charset) throws UnsupportedEncodingException {
        if (null == charset) {
            charset = DEFAULT_CHARSET;
        }
        return stringInfo.getBytes(charset);
    }
    public static boolean verify(String plainInfo, byte[] signedInfo, PublicKey publicKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Signature checker = Signature.getInstance(publicKey.getAlgorithm(), bcProvider);
        checker.initVerify(publicKey);
        checker.update(plainInfo.getBytes());
        return checker.verify(signedInfo);
    }

    public static boolean verify(String plainInfo, String signedInfoBase64, PublicKey publicKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        byte[] signedInfoBytes = Base64.decodeBase64(signedInfoBase64);
        return verify(plainInfo, signedInfoBytes, publicKey);
    }

    public static boolean verify(String plainInfo, byte[] signedInfo, Certificate cert)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Signature checker = Signature.getInstance(cert.getPublicKey().getAlgorithm(), bcProvider);
        checker.initVerify(cert);
        checker.update(plainInfo.getBytes());
        return checker.verify(signedInfo);
    }

    public static boolean verify(String plainInfo, String signedInfoBase64, Certificate cert)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        byte[] signedInfoBytes = Base64.decodeBase64(signedInfoBase64);
        return verify(plainInfo, signedInfoBytes, cert);
    }

    public static KeyStore loadP12(InputStream p12IS, String password) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {
        KeyStore keyStore = KeyStore.getInstance("PKCS12", bcProvider);
        keyStore.load(p12IS, password.toCharArray());
        return keyStore;
    }

    public static Certificate[] loadCertificateChainFromP12(InputStream p12IS, String password,
                                                            String certificateChainAlias) throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
            IOException {
        KeyStore keyStore = loadP12(p12IS, password);
        return keyStore.getCertificateChain(certificateChainAlias);
    }
    public static KeyStore loadJKS(InputStream jksIS, String storepass) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {
        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(jksIS, storepass.toCharArray());
        return keyStore;
    }

    public static KeyStore loadJKS(File jksFile, String storepass) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(jksFile);
            return loadJKS(fis, storepass);
        } finally {
            IOUtil.close(fis);
        }
    }

    public static KeyStore loadJKS(String jksFileBase64, String storepass) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {
        InputStream is = null;
        try {
            is = IOUtil.base64StringToStream(jksFileBase64);
            return loadJKS(is, storepass);
        } finally {
            IOUtil.close(is);
        }
    }
    public static KeyStore loadP12(File p12File, String password) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(p12File);
            return loadP12(fis, password);
        } finally {
            IOUtil.close(fis);
        }
    }

    public static Certificate[] loadCertificateChainFromP12(File p12File, String password,
                                                            String certificateChainAlias) throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
            IOException {
        KeyStore keyStore = loadP12(p12File, password);
        return keyStore.getCertificateChain(certificateChainAlias);
    }

    @SuppressWarnings("unchecked")
    public static Collection<X509Certificate> parseX509FromP7(InputStream p7IS) {
        try {
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509", bcProvider);
            return (Collection<X509Certificate>) certFactory.generateCertificates(p7IS);
        } catch (Exception e) {
            LOG.error("parseX509FromP7 failed", e);
            return null;
        }
    }

    public static Collection<X509Certificate> parseX509FromP7(String p7Base64) {
        InputStream p7IS = null;
        try {
            p7IS = IOUtil.base64StringToStream(p7Base64);
            return parseX509FromP7(p7IS);
        } finally {
            IOUtil.close(p7IS);
        }
    }

    /**
     * 从证书上获取公钥
     * @param certificate
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PublicKey getX509PublicKeyFromCertificate(Certificate certificate) throws KeyStoreException,
            NoSuchAlgorithmException, InvalidKeySpecException {
        PublicKey publicKey = certificate.getPublicKey();

        KeyFactory keyFactory = KeyFactory.getInstance(publicKey.getAlgorithm());

        byte[] encodedPubKeyBytes = publicKey.getEncoded();
        X509EncodedKeySpec x509PubKeySpec = new X509EncodedKeySpec(encodedPubKeyBytes);
        PublicKey x509PubKey = keyFactory.generatePublic(x509PubKeySpec);
        return x509PubKey;
    }

    /**
     * 从证书上获取私钥
     * @param keyStore
     * @param certificateChainAlias
     * @return
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PublicKey getX509PublicKeyFromKeyStore(KeyStore keyStore, String certificateChainAlias)
            throws KeyStoreException, NoSuchAlgorithmException, InvalidKeySpecException {
        Certificate certificate = keyStore.getCertificate(certificateChainAlias);
        return getX509PublicKeyFromCertificate(certificate);
    }

    /**
     *
     * @param keyStore
     * @param keypass
     * @return
     * @throws KeyStoreException
     * @throws UnrecoverableKeyException
     * @throws NoSuchAlgorithmException
     */
    public static PrivateKey getPrivateKeyFromKeyStore(KeyStore keyStore, String keypass) throws KeyStoreException,
            UnrecoverableKeyException, NoSuchAlgorithmException {
        Enumeration<String> aliases = keyStore.aliases();
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            if (keyStore.isKeyEntry(alias)) {
                return (PrivateKey) keyStore.getKey(alias, keypass.toCharArray());
            }
        }
        return null;
    }
    /**
     * 获取证书上的SN
     * @param cert  证书
     * @return
     */
    public static String getSN(X509Certificate cert) {
        String sn = cert.getSerialNumber().toString(16).toUpperCase();
        if (sn.length() % 2 != 0) {
            sn = "0" + sn;
        }
        return sn;
    }

    /**
     * 获取证书上的DN
     * @param cert
     * @return
     */
    public static String getDN(X509Certificate cert) {
        return cert.getSubjectX500Principal().getName();
    }

    private static ASN1Sequence derOctetStringToASN1Sequence(byte[] derOctetStringBytes) {
        if (null != derOctetStringBytes) {
            ASN1InputStream ais = null;
            try {
                ASN1OctetString octs = ASN1OctetString.getInstance(derOctetStringBytes);
                ais = new ASN1InputStream(octs.getOctets());
                return (ASN1Sequence) ais.readObject();
            } catch (Exception e) {
                LOG.error("derOctetStringToASN1Sequence error", e);
            } finally {
                IOUtil.close(ais);
            }
        }
        return null;
    }

    public static CRLDistPoint getCrlDistPoint(X509Certificate certificate) {
        byte[] crlPointBytes = certificate.getExtensionValue(Extension.cRLDistributionPoints.getId());
        if (null != crlPointBytes) {
            ASN1Sequence as = derOctetStringToASN1Sequence(crlPointBytes);
            if (null != as) {
                return CRLDistPoint.getInstance(as);
            }
        }
        return null;
    }

    /**
     * 获取叶子节点的证书
     * @param certificates 证书列表
     * @return
     */
    public static X509Certificate getLeafCertificate(Collection<X509Certificate> certificates) {
        X509Certificate rootCell = null;
        X509Certificate lastCell = null;
        if (null != certificates && !certificates.isEmpty()) {
            for (X509Certificate certificate : certificates) {
                rootCell = certificate;
                lastCell = certificate;
                break;
            }
            while ((rootCell = getChildNode(rootCell, certificates)) != null) {
                lastCell = rootCell;
            }
            return lastCell;
        }else{
            return null;
        }
    }

    /**
     * 直接通过P7B64解析并返回叶子节点的证书
     * @param p7Base64
     * @return
     */
    public static X509Certificate parseLeafX509FromP7(String p7Base64) {
        Collection<X509Certificate> certificates = parseX509FromP7(p7Base64);
        return getLeafCertificate(certificates);
    }


    /**
     *
     * @param c  父节点
     * @param certificates 所有节点列表  注意根节点SubjectDN与IssuerDN一致问题
     * @return
     */
    private static X509Certificate getChildNode(X509Certificate c, Collection<X509Certificate> certificates){
        for (X509Certificate certificate : certificates) {
            //父节点的subjectdn = 子节点的issuerdn
            if(c.getSubjectDN().toString().equals(certificate.getIssuerDN().toString())&&(!certificate.getSubjectDN().toString().equals(certificate.getIssuerDN().toString()))){
                return certificate;
            }
        }
        return null;
    }

}
